#include "stdafx.h"
#include "HXDisplay.h"
#include "HXWindow.h"

// need for UpdateBackground:
#include "HXConfig.h"
#include "HXLink.h"

#pragma warning(disable : 4005)

// OpenGL Header Files:
#define FTGL_LIBRARY_STATIC
#include "FTGL/ftgl.h"

//-----------------------------------------------------------------------------

void SetVSync(bool bSync)
{
	typedef bool (APIENTRY *PFNWGLSWAPINTERVALFARPROC)(int);

	PFNWGLSWAPINTERVALFARPROC wglSwapIntervalEXT = 0;

	wglSwapIntervalEXT = (PFNWGLSWAPINTERVALFARPROC)wglGetProcAddress("wglSwapIntervalEXT");

	if (wglSwapIntervalEXT)
		wglSwapIntervalEXT(bSync);
}

//-----------------------------------------------------------------------------

float HXMorphData::GetAmplitude() 
{
	float sum_value = other+emote+idle+speech;
	float max_value = idle;
	if (abs(speech) > max_value) max_value = abs(speech);
	if (abs(emote) > max_value) max_value = abs(emote);
	if (abs(other) > max_value) max_value = abs(other);
	if (abs(sum_value) <= max_value)
	{
		return sum_value;
	}
	else if (sum_value < 0)
	{
		return -max_value;
	}
	else
	{
		return max_value;
	}
}

//! Constructor
HXDisplay::HXDisplay()
: m_hdc(0),
  m_hglrc(0),
  m_hwnd(0),
  m_pImage(NULL),
  m_nearZ(0.75f),
  m_farZ(2.75f),
  m_width(0),
  m_height(0),
  m_pFont(NULL),
  m_pFontBuffer(NULL),
  m_pFontBufferSize(0),
  m_oglTexName(0),
  m_pConfig(NULL),
  m_pAlive(NULL),
  m_UpdatePriorityObject(NULL),
  m_hMutex(0),
  m_lockCount(0),
  m_bSuspendPaint(false),
  m_bOpenGL2Supported(false),
  m_bFBOArbSupported(false),
  m_bFBOExtSupported(false),
  m_bDepthPeelingSupported(false),
  m_mainFboId(0),
  m_mainColorTexId(0),
  m_mainDepthTexId(0),
  m_dualBackBlenderFboId(0),
  m_dualPeelingSingleFboId(0),
  m_dualBackBlenderTexId(0),
  m_quadDisplayList(0),
  m_queryId(0),
  m_frontColorBlenderFboId(0)
{
	m_hMutex = CreateMutex(NULL, FALSE, NULL);
	ResetRemoteMorphs();
}

//! Destructor
HXDisplay::~HXDisplay()
{
	UnloadFont();
	delete [] m_pFontBuffer; 
	m_pFontBuffer = NULL;
	ReleaseMutex(m_hMutex);
}

//****************************************************************************

bool HXBufferImage::Create(int _width, int _height)
{
    width = _width;
    height = _height;
    pitch = ((width * 32 + 31) & ~31) >> 3;
    pPixels = NULL;
    hdc = CreateCompatibleDC(NULL);

    if (!hdc) return false;

	pTempPixels = new BYTE[width * height * 4];

    memset(&info, 0, sizeof(info));

    info.bmiHeader.biSize = sizeof(info.bmiHeader);
    info.bmiHeader.biBitCount = 32;
    info.bmiHeader.biWidth = width;
    info.bmiHeader.biHeight = -height;
    info.bmiHeader.biCompression = BI_RGB;
    info.bmiHeader.biPlanes = 1;

    hBitmap = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void**)&pPixels, NULL, 0);

    if (!hBitmap)
    {
        Destroy();
        return false;
    }

    GdiFlush();
    return true;
}

void HXBufferImage::Destroy()
{
    width = 0;
    height = 0;
    pitch = 0;

    if (hBitmap)
    {
        DeleteObject(hBitmap);
        hBitmap = NULL;
    }

    if (hdc)
    {
        DeleteDC(hdc);
        hdc = NULL;
    }

    memset(&info, 0, sizeof(info));
    pPixels = NULL;

	delete [] pTempPixels;
	pTempPixels = NULL;
}

void HXBufferImage::PreMultAlpha()
{
    // The per pixel alpha blending API for layered windows deals with
    // pre-multiplied alpha values in the RGB channels. For further details see
    // the MSDN documentation for the BLENDFUNCTION structure. It basically
    // means we have to multiply each red, green, and blue channel in our image
    // with the alpha value divided by 255.
    //
    // Notes:
    // 1. ImagePreMultAlpha() needs to be called before every call to
    //    UpdateLayeredWindow() (in the RedrawLayeredWindow() function).
    //
    // 2. Must divide by 255.0 instead of 255 to prevent alpha values in range
    //    [1, 254] from causing the pixel to become black. This will cause a
    //    conversion from 'float' to 'BYTE' possible loss of data warning which
    //    can be safely ignored.

    BYTE *pPixel = NULL;

    if (width * 4 == pitch)
    {
        // This is a special case. When the image width is already a multiple
        // of 4 the image does not require any padding bytes at the end of each
        // scan line. Consequently we do not need to address each scan line
        // separately. This is much faster than the below case where the image
        // width is not a multiple of 4.
        
        int totalBytes = width * height * 4;

        for (int i = 0; i < totalBytes; i += 4)
        {
            pPixel = &pPixels[i];
			BYTE temp = pPixel[3];
			pPixel[0] = (BYTE)(pPixel[0] * (float)temp / 255.0f);
			pPixel[1] = (BYTE)(pPixel[1] * (float)temp / 255.0f);
			pPixel[2] = (BYTE)(pPixel[2] * (float)temp / 255.0f);	
			pPixel[3] = temp;
			/*pPixel[0] = (BYTE)(pPixel[0] * (float)pPixel[3] / 255.0f);
			pPixel[1] = (BYTE)(pPixel[1] * (float)pPixel[3] / 255.0f);
			pPixel[2] = (BYTE)(pPixel[2] * (float)pPixel[3] / 255.0f);*/
        }
    }
    else
    {
        // Width of the image is not a multiple of 4. So padding bytes have
        // been included in the DIB's pixel data. Need to address each scan
        // line separately. This is much slower than the above case where the
        // width of the image is already a multiple of 4.

        for (int y = 0; y < height; ++y)
        {
            for (int x = 0; x < width; ++x)
            {
                pPixel = &pPixels[(y * pitch) + (x * 4)];
				BYTE temp = pPixel[3];
			pPixel[0] = (BYTE)(pPixel[0] * (float)temp / 255.0f);
			pPixel[1] = (BYTE)(pPixel[1] * (float)temp / 255.0f);
			pPixel[2] = (BYTE)(pPixel[2] * (float)temp / 255.0f);	
			pPixel[3] = temp;
				/*pPixel[0] = (BYTE)(pPixel[0] * (float)pPixel[3] / 255.0f);
				pPixel[1] = (BYTE)(pPixel[1] * (float)pPixel[3] / 255.0f);
				pPixel[2] = (BYTE)(pPixel[2] * (float)pPixel[3] / 255.0f);*/
            }
        }
    }
}

// for debugging purposes only
size_t HXBufferImage::CountFilled(bool useTemp)
{
	int totalBytes = width * height * 4;
	size_t filled = 0;
	BYTE lastpixel = 0;
    for (int i = 0; i < totalBytes; i += 4)
    {
		BYTE *pPixel = (useTemp ? &pTempPixels[i] : &pPixels[i]);
		if (i > 0)
		{
			if (lastpixel != pPixel[0])
			{
				filled++;
			}
		}
		lastpixel = pPixel[0];
	}
	return filled;
}

//-----------------------------------------------------------------------------

//! Grants ownership of the display to the calling thread.
void HXDisplay::Lock()
{
	DWORD dwWaitResult; 
    dwWaitResult = WaitForSingleObject( // request ownership of mutex
					m_hMutex,   // handle to mutex
					INFINITE);  // no time-out interval
	m_lockCount++;
}

//! Grants ownership of the display to the calling thread. Does not wait.
bool HXDisplay::LockNow()
{
	DWORD dwWaitResult; 
    dwWaitResult = WaitForSingleObject( // request ownership of mutex
					m_hMutex,   // handle to mutex
					0);  // return immediately
	switch (dwWaitResult) 
    {
		// thread got ownership of the mutex
        case WAIT_OBJECT_0:
			m_lockCount++;
			return true;
		// time-out interval elapsed
		case WAIT_ABANDONED:
		case WAIT_TIMEOUT:
		default:
			return false;
    }
}

//! Releases ownership of the display.
void HXDisplay::Unlock()
{
	if (m_lockCount)
	{
		// Release ownership of the mutex
		m_lockCount--;
		ReleaseMutex(m_hMutex);
	}
}

//****************************************************************************

//! Loads parameters from configuration file.
size_t HXDisplay::LoadFromConfig(HXPlayerConfig& config)
{
	SUSPENDPAINT;
	ClearMorphCache();
	// load models
	size_t loaded = LoadModelGroup(config.resourceGroup);
	CreateMorphCache();
	// load background
	if (config.backgroundName.size())
		LoadBackground(config.backgroundName);
	else
		UnloadBackground();
	// load font
	if (config.subtitleFont.size())
		LoadFont(config.subtitleFont, static_cast<unsigned int>(config.subtitleFontSize));
	// keep a pointer to the config
	m_pConfig = &config;
	// load lights
	ResetLights(true);
	g_window.ShowStatus(L"Loaded %d model(s) from configuration", loaded);
	UNSUSPENDPAINT;
	return loaded;
}

//! Loads models from a resource group.
size_t HXDisplay::LoadModelGroup(HXResourceGroup& group)
{
	size_t loaded = 0;
	list<HXResourceGroup>::iterator groupIt = group.children.begin();
	while (groupIt != group.children.end())
	{
		loaded += LoadModelGroup(*groupIt);
		++groupIt;
	}
	list<HXResource>::iterator modelIt = group.resources.begin();
	while (modelIt != group.resources.end())
	{
		if (!modelIt->IsLoaded())
		{
			if (modelIt->item >= 0)
			{
				// single model in file
				if (LoadModel(*modelIt))
				{
					modelIt->renderIndex = ModelSize()-1;
					modelIt->pRender = &m_renderList.back();
					loaded++;
					++modelIt;
				}
				else
				{
					modelIt = group.resources.erase(modelIt);
				}
			}
			else
			{
				// multiple accessories in file
				if (LoadModel(*modelIt))
				{
					modelIt->renderIndex = ModelSize()-1;
					modelIt->pRender = &m_renderList.back();
					loaded++;
					if (modelIt->item == -1) // not a single model file
					{
						modelIt->item = 0;
						bool more = true;
						size_t nextItem = 1;
						while (more)
						{
							HXResource addModel = *modelIt;
							addModel.item = nextItem++;
							if (LoadModel(addModel))
							{
								addModel.renderIndex = ModelSize()-1;
								addModel.pRender = &m_renderList.back();
								group.resources.push_front(addModel);
								loaded++;
							}
							else
							{
								more = false;
							}
						}
					}
					++modelIt;
				}
				else
				{
					modelIt = group.resources.erase(modelIt);
				}
			}
		}
		else
		{
			++modelIt;
		}
	}
	g_window.ShowStatus(L"Loaded %d model(s) from resource group", loaded);
	return loaded;
}

//! Removes a 3D model at the specified index.
bool HXDisplay::UnloadSingleModel(size_t idx, HXResourceGroup& group)
{
	SUSPENDPAINT;
	ClearMorphCache();
	bool result = UnloadModel(idx, group);
	CreateMorphCache();
	UNSUSPENDPAINT;
	#ifndef _DEBUG // same status message as for UnloadModel
		if (result) g_window.ShowStatus(L"Removed model #%d", idx);
	#endif
	return result;
}

//! Removes a group of 3D models belonging to a model group.
bool HXDisplay::UnloadModelGroup(wstring& groupName, HXResourceGroup& group)
{
	list<HXResource*> resPtrs;
	HXResourceGroup* pGroup = g_config.resourceGroup.GetDescendant(groupName);
	if (pGroup)
	{
		pGroup->Expand(resPtrs);
	}
	bool result = (resPtrs.size() ? true : false);
	if (result)
	{
		SUSPENDPAINT;
		ClearMorphCache();
		for (list<HXResource*>::iterator rpIt = resPtrs.begin(); (rpIt != resPtrs.end()) && result; ++rpIt)
		{
			result &= UnloadModel((*rpIt)->renderIndex, group);
		}
		CreateMorphCache();
		UNSUSPENDPAINT;
	}
	if (result) g_window.ShowStatus(L"Removed model group \"%s\"", groupName.c_str());
	return result;
}

bool HXDisplay::UnloadModel(size_t idx, HXResourceGroup& group)
{
	list<HXResourceGroup>::iterator groupIt = group.children.begin();
	while (groupIt != group.children.end())
	{
		if (UnloadModel(idx, *groupIt))
		{
			return true;
		}
		++groupIt;
	}

	list<HXResource>::iterator modelIt = group.resources.begin();
	while (modelIt != group.resources.end())
	{
		if (modelIt->renderIndex == (int)idx)
		{
			modelIt = group.resources.erase(modelIt);
			list<HXRenderData>::iterator rdit = m_renderList.begin();
			for (size_t i = 0; rdit != m_renderList.end(); i++)
			{
				if (i == idx)
				{
					rdit = m_renderList.erase(rdit);
					#ifdef _DEBUG
					g_window.ShowStatus(L"Removed model #%d", idx);
					#endif
					HXResourceGroup* pRoot = &group;
					while (pRoot->pParent)
					{
						pRoot = pRoot->pParent;
					}
					pRoot->RemoveRenderIndex((int)idx);
					return true;
				}
				else
				{
					++rdit;
				}
			}
		}
		else
		{
			++modelIt;
		}
	}

	return false;
}

//! Removes all 3D models of a given group.
void HXDisplay::UnloadAllModels(HXResourceGroup& group)
{
	SUSPENDPAINT;
	ClearMorphCache();
	list<HXResourceGroup>::reverse_iterator groupIt = group.children.rbegin();
	while (groupIt != group.children.rend())
	{
		UnloadAllModels(*groupIt);
		++groupIt;
	}
	group.children.clear();

	size_t count = 0;
	while (true)
	{
		list<HXResource>::reverse_iterator modelIt = group.resources.rbegin();
		if (modelIt == group.resources.rend())
		{
			break;
		}
		else
		{
			UnloadModel(modelIt->renderIndex, group);
			count++;
		}
	}
	if (count)
		g_window.ShowStatus(L"Removed all (%d) models", count);
	UNSUSPENDPAINT;
}

//****************************************************************************

//! Retrieves a loaded model.
inline
HXRenderData* HXDisplay::GetModel(size_t idx)
{
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (size_t i = 0; rdit != m_renderList.end(); ++rdit, i++)
	{
		if (i == idx)
			return &(*rdit);
	}
	return NULL;
}

//****************************************************************************

//! Loads a 3D model from an HXResource.
bool HXDisplay::LoadModel(HXResource& res)
{
	if (res.modelName.empty()) return false;
	TCHAR cstr[MAX_PATH]=TEXT(""); 
	#ifdef _DEBUG
	DWORD retval = GetFullPathName(res.modelName.c_str(), MAX_PATH, cstr, NULL);
    if (retval == 0) WNotify(NULL, L"GetFullPathName failed (%d)\n", GetLastError());        
	#else
	GetFullPathName(res.modelName.c_str(), MAX_PATH, cstr, NULL);
	#endif
	PathRemoveExtension(cstr);
	if (PathFileExists((cstr+wstring(L".hx3")).c_str()))
	{
		wstring modelFile(cstr+wstring(L".hx3"));
		size_t item = (res.item >= 0) ? res.item : 0;
		return LoadHxModel(modelFile, item, res.shininess, res.alpha_threshold, res.center, res.recomp_normal);
	}
	else
	if (PathFileExists((cstr+wstring(L".obj")).c_str()))
	{
		wstring modelFile(cstr+wstring(L".obj"));
		res.item = 0; // tell LoadModelGroup that there's only one model in this file
		return LoadObjModel(res);
	}
	else
	{
		g_window.ShowErrorStatus(L"\"%s\" must either be a Head X archive (with extension .HX3) or a Wavefront OBJ (with extension .OBJ) - not found!", res.textureName.c_str());
		return false;
	}
}

//! Load a 3D model from triangle and texture files.
bool HXDisplay::LoadHxModel(const wstring& modelFile, size_t item, float shininess, float alpha_threshold, bool center, int recomp_normal)
{
	SUSPENDPAINT;
    m_renderList.push_back(HXRenderData());
	
	// load model
	if (m_renderList.back().Model().LoadFromFile(modelFile, item))
	{
		if (shininess >= 0.0f)
			m_renderList.back().SetShininess(shininess);
		if (alpha_threshold >= 0.0f)
			m_renderList.back().SetAlphaThreshold(alpha_threshold);
		m_renderList.back().Center() = center;
		m_renderList.back().RecompNormal() = recomp_normal;
		m_renderList.back().CreateMorphs();
		g_window.ShowStatus(L"Loaded HX3 model from: \"%s\"", modelFile.c_str());
		UNSUSPENDPAINT;
		return true;
	}
	else
	{
		if (!item) g_window.ShowErrorStatus(L"Failed to load \"%s\"", modelFile.c_str());
		m_renderList.pop_back();
		UNSUSPENDPAINT;
		return false;
	}
}

//! Load a 3D model from Wavefront OBJ files.
bool HXDisplay::LoadObjModel(const HXResource& res)
{
	SUSPENDPAINT;
    m_renderList.push_back(HXRenderData());

	// get filename (again)
	TCHAR cstr[MAX_PATH]=TEXT(""); 
	GetFullPathName(res.modelName.c_str(), MAX_PATH, cstr, NULL);
	PathRemoveExtension(cstr);
	wstring modelFile(cstr+wstring(L".obj"));
	
	// load model
	if (m_renderList.back().Model().ImportFromObjFile(modelFile))
	{
		g_window.ShowStatus(L"Loaded OBJ model (base) from: \"%s\"", modelFile.c_str());
		if (res.shininess >= 0.0f)
			m_renderList.back().SetShininess(res.shininess);
		if (res.alpha_threshold >= 0.0f)
			m_renderList.back().SetAlphaThreshold(res.alpha_threshold);
		m_renderList.back().Center() = res.center;
		m_renderList.back().RecompNormal() = res.recomp_normal;
		// load blendshapes
		m_renderList.back().Model().diffMorphList.reserve(res.blendList.size());
		for (list<HXBlend>::const_iterator blIt = res.blendList.begin(); blIt != res.blendList.end(); ++blIt)
		{
			if (!blIt->isMaterialBlend)
			{
				GetFullPathName(blIt->fileName.c_str(), MAX_PATH, cstr, NULL);
				PathRemoveExtension(cstr);
				wstring blendFile(cstr+wstring(L".obj"));
				if (m_renderList.back().Model().ImportObjBlendShape(blIt->id, blendFile))
				{
					g_window.ShowStatus(L"Loaded OBJ model (blendshape) from: \"%s\"", blendFile.c_str());
				}
				else
				{
					g_window.ShowErrorStatus(L"Failed to load \"%s\" - will not attempt to load further blendshapes", blendFile.c_str());
					break;
				}
			}
		}
		m_renderList.back().CreateMorphs();
		UNSUSPENDPAINT;
		return true;
	}
	else
	{
		g_window.ShowErrorStatus(L"Failed to load \"%s\"", modelFile.c_str());
		m_renderList.pop_back();
		UNSUSPENDPAINT;
		return false;
	}
}

//! Save all models to a new HX3 file.
bool HXDisplay::ExportModels(const wstring& filePath)
{
	SUSPENDPAINT;
	list<HXModel*> modelList;
	list<HXRenderData>::iterator rendit = m_renderList.begin();
	for (; rendit != m_renderList.end(); ++rendit)
	{
		modelList.push_back(&rendit->Model());
	}
	if (HXModel::SaveToFile(filePath, modelList))
	{
		UNSUSPENDPAINT;
		return true;
	}
	UNSUSPENDPAINT;
	return false;
}

//****************************************************************************

//! Load a font from a file.
bool HXDisplay::LoadFont(const wstring& fontFile, unsigned int size)
{
	delete [] m_pFontBuffer; 
	m_pFontBuffer = NULL;
	m_pFontBufferSize = 0;
	HANDLE fh = CreateFile(fontFile.c_str(),
						   GENERIC_READ,
						   0,
						   NULL,
						   OPEN_EXISTING,
						   FILE_ATTRIBUTE_NORMAL,
						   NULL);
	if (fh != INVALID_HANDLE_VALUE)
	{
		m_pFontBufferSize = GetFileSize(fh, NULL);
		m_pFontBuffer = new unsigned char[m_pFontBufferSize];
		DWORD bytesRead;
		if (ReadFile(fh, m_pFontBuffer, m_pFontBufferSize, &bytesRead, NULL))
		{
			CloseHandle(fh);
			if ((bytesRead == m_pFontBufferSize) && InitFont(size))
			{
				return true;
			}
		}
		CloseHandle(fh);
		delete [] m_pFontBuffer; 
		m_pFontBuffer = NULL;
		m_pFontBufferSize = 0;
	}
	g_window.ShowErrorStatus(L"Invalid font file \"%s\"", fontFile.c_str());
	return false;
}

//! Delete the current font
void HXDisplay::UnloadFont()
{
	SAFE_DELETE(m_pFont);
	g_window.ShowStatus(L"Unloaded subtitle font");
}

bool HXDisplay::InitFont(unsigned int size)
{
	SAFE_DELETE(m_pFont);
	m_pFont = new FTPolygonFont(m_pFontBuffer, m_pFontBufferSize);
	if (m_pFont->Error())
	{
		SAFE_DELETE(m_pFont);
		return false;
	}
	m_pFont->FaceSize(size);
    m_pFont->CharMap(ft_encoding_unicode);
	//m_pFont->Depth(3);
	m_pFont->Outset(-0.5, 1.5);
	return true;
}

//! Load a background from a file.
bool HXDisplay::LoadBackground(const wstring& bmpFile)
{
	//SUSPENDPAINT;
	UnloadBackground();
    if (!m_background.LoadFromFile(bmpFile))
    {
		g_window.ShowErrorStatus(L"Invalid background \"%s\"", bmpFile.c_str());
		//UNSUSPENDPAINT;
		return false;
    }
	if ((m_background.GetHeight() % 2) || (m_background.GetWidth() % 2))
	{
		g_window.ShowErrorStatus(L"Background \"%s\" has wrong size - must have height and width divisable by 2", bmpFile.c_str());
		UnloadBackground();
		return false;
	}
	m_background.FlipHorizontal();
	//UNSUSPENDPAINT;
	return true;
}

//! Remove the background.
void HXDisplay::UnloadBackground()
{
	m_background.Clear();
	BuildBackground();
}

void HXDisplay::UpdateBackground()
{
	if (g_link.BackgroundStatus() != HXDYB_NONE)
	{
		if (g_link.BackgroundStatus() == HXDYB_NEW)
		{
			if (m_oglTexName != 0)
			{
				glDeleteTextures(1, &m_oglTexName);
				m_oglTexName = 0;
			}
			if (!m_background.IsEmpty())
			{
				glEnable(GL_TEXTURE_2D);
				glGenTextures( 1, (GLuint*) &m_oglTexName );
				glBindTexture( GL_TEXTURE_2D, m_oglTexName );
				glPixelStorei(GL_UNPACK_ALIGNMENT,8);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
				glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
				HXRGBA* p = g_link.GrabBackground();
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_config.dynBGX, g_config.dynBGY, 0, GL_RGBA, GL_UNSIGNED_BYTE, p);
				g_link.DoneWithBackground();
			}
			g_link.BackgroundStatus() = HXDYB_OLD;
		}
		else if (g_link.IsBackgroundModified())
		{
			if (m_oglTexName != 0)
			{
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, m_oglTexName);
				GLint val = 0;
				glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &val);
				if (static_cast<size_t>(g_config.dynBGX) == static_cast<size_t>(val))
				{
					glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &val);
					if (static_cast<size_t>(g_config.dynBGY) == static_cast<size_t>(val))
					{
						HXRGBA* p = g_link.GrabBackground();
						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_config.dynBGX, g_config.dynBGY, GL_RGBA, GL_UNSIGNED_BYTE, p);
						g_link.DoneWithBackground();
						//int oglError = glGetError();
						//if (oglError) {}
						return;
					}
				}
			}
		}
	}
}

//! Make the background ready for rendering.
void HXDisplay::BuildBackground()
{
	// Background rendering:
	if (m_oglTexName != 0)
	{
		glDeleteTextures(1, &m_oglTexName);
		m_oglTexName = 0;
	}
	if (!m_background.IsEmpty())
	{
		glEnable(GL_TEXTURE_2D);
		glGenTextures( 1, (GLuint*) &m_oglTexName );
		glBindTexture( GL_TEXTURE_2D, m_oglTexName );
		glPixelStorei(GL_UNPACK_ALIGNMENT,8);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_background.GetWidth(), m_background.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_background.GetData());
	}
}

//****************************************************************************

//! Initialise the 3D rendering display.
bool HXDisplay::CreateDisplay(HWND hwnd, bool bLayered)
{
	SUSPENDPAINT;

	m_hwnd = hwnd;

	if (bLayered) // TRANSPARENT WINDOW
	{
	    PIXELFORMATDESCRIPTOR pfd = {0};
		pfd.nSize      = sizeof(pfd);
		pfd.nVersion   = 1;
		pfd.dwFlags    = PFD_DRAW_TO_WINDOW
					   | PFD_SUPPORT_OPENGL
					   | PFD_DOUBLEBUFFER;
		pfd.iPixelType = PFD_TYPE_RGBA;
		pfd.cColorBits = 32;
		pfd.cAlphaBits = 8;
		pfd.cDepthBits = 24;
		pfd.cStencilBits = 8;

		if (!(m_hdc = GetDC(m_hwnd)))
			goto clean_display;

		int pf = ChoosePixelFormat(m_hdc, &pfd);

		if (!SetPixelFormat(m_hdc, pf, &pfd))
			goto clean_display;

		if (!(m_hglrc = wglCreateContext(m_hdc)))
			goto clean_display;

		if (!wglMakeCurrent(m_hdc, m_hglrc))
			goto clean_display;

		// advanced graphics...
		GLenum err = glewInit();
		if (GLEW_OK != err)
		{
			g_window.ShowErrorStatus(L"GLEW initialisation failed");
			goto clean_display;
		}
		else
		{
			m_bOpenGL2Supported = (!g_config.disableShaders && glewIsSupported("GL_VERSION_2_0")) ? true : false;
			m_bFBOArbSupported = glewIsSupported("GL_ARB_framebuffer_object") ? true : false;
			m_bFBOExtSupported = glewIsSupported("GL_EXT_framebuffer_object") ? true : false;
			if (!(m_bOpenGL2Supported && (m_bFBOArbSupported || m_bFBOExtSupported)))
			{
				goto clean_display;
			}
			else
			{
				m_bDepthPeelingSupported = g_config.enableDepthShaders && m_bFBOArbSupported 
					                       &&
										   glewIsSupported(	"GL_ARB_texture_rectangle "
															"GL_ARB_texture_float "
															"GL_ARB_color_buffer_float "
															"GL_ARB_depth_buffer_float ");

				// FBO initialization for off-screen rendering
				int attribList[] =
				{
					WGL_SUPPORT_OPENGL_ARB,  TRUE,      // associate with OpenGL
					WGL_DOUBLE_BUFFER_ARB,   FALSE,     // single buffered
					WGL_RED_BITS_ARB,   8,              // minimum 8-bits for red channel
					WGL_GREEN_BITS_ARB, 8,              // minimum 8-bits for green channel
					WGL_BLUE_BITS_ARB, 8,               // minimum 8-bits for blue channel
					WGL_ALPHA_BITS_ARB, 8,              // minimum 8-bits for alpha channel
					WGL_DEPTH_BITS_ARB, 16,             // minimum 16-bits for depth buffer
					0
				};

				int format = 0;
				UINT matchingFormats = 0;

				if (!wglChoosePixelFormatARB(m_hdc, attribList, 0, 1, &format, &matchingFormats))
				{
					g_window.ShowErrorStatus(L"wglChoosePixelFormatARB() failed");
					goto clean_display;
				}

				// kill the old rendering context
				wglMakeCurrent(m_hdc, 0);
				wglDeleteContext(m_hglrc);

				if (!(m_hglrc = wglCreateContext(m_hdc)))
				{
					g_window.ShowErrorStatus(L"wglCreateContext() failed");
					goto clean_display;
				}

				if (!wglMakeCurrent(m_hdc, m_hglrc))
					goto clean_display;

				// GLEW re-init
				err = glewInit();
				if (GLEW_OK != err)
				{
					g_window.ShowErrorStatus(L"GLEW initialisation (for FBO context) failed");
					goto clean_display;
				}

				InitMainFBO();

			}
		}

	}
	else // NORMAL WINDOW
	{
		HWND tempWnd = CreateWindow(g_main.szWindowClass, g_main.szTitle, 0, 0, 0, 640, 480, NULL, NULL, g_main.hInst, NULL );
		m_hdc = GetDC(tempWnd);

		PIXELFORMATDESCRIPTOR pfd = {0};
		pfd.nSize      = sizeof(pfd);
		pfd.nVersion   = 1;
		pfd.dwFlags    = PFD_DRAW_TO_WINDOW
					   | PFD_SUPPORT_OPENGL
					   | PFD_DOUBLEBUFFER;
		pfd.iPixelType = PFD_TYPE_RGBA;
		pfd.cColorBits = 32;
		pfd.cAlphaBits = 8;
		pfd.cDepthBits = 24;
		pfd.cStencilBits = 8;

		int format = ChoosePixelFormat(m_hdc,&pfd);

		if (!(SetPixelFormat(m_hdc,format,&pfd) && DescribePixelFormat(m_hdc,format,pfd.nSize,&pfd)))
		{
			DestroyWindow(tempWnd);
			goto clean_display;
		}

		m_hglrc = wglCreateContext(m_hdc);
		if (!m_hglrc)
		{
			DestroyWindow(tempWnd);
			goto clean_display;
		}

		if (!wglMakeCurrent(m_hdc, m_hglrc))
		{
			DestroyWindow(tempWnd);
			goto clean_display;
		}

		// advanced graphics support...
		GLenum err = glewInit();
		if (GLEW_OK != err)
		{
			g_window.ShowErrorStatus(L"GLEW initialisation failed");
			goto clean_display;
		}
		else
		{
			if (m_bOpenGL2Supported = (!g_config.disableShaders && glewIsSupported("GL_VERSION_2_0")) ? true : false)
			{
				m_bFBOArbSupported = glewIsSupported("GL_ARB_framebuffer_object") ? true : false;
				m_bFBOExtSupported = glewIsSupported("GL_EXT_framebuffer_object") ? true : false;
				m_bDepthPeelingSupported = g_config.enableDepthShaders && m_bFBOArbSupported 
					                       &&
										   glewIsSupported(	"GL_ARB_texture_rectangle "
															"GL_ARB_texture_float "
															"GL_ARB_color_buffer_float "
															"GL_ARB_depth_buffer_float ");
											
				/*m_bDepthPeelingSupported = (glewIsSupported(	"GL_ARB_texture_rectangle "
																"GL_ARB_texture_float ")
											&&
											(
												glewIsSupported("GL_EXT_framebuffer_object ")
												||
												glewIsSupported("GL_ARB_framebuffer_object ")
																
											)
											&&
											(
											glewIsSupported(	"GL_ARB_color_buffer_float "
																"GL_ARB_depth_buffer_float ")
											||
											glewIsSupported(	"GL_NV_float_buffer "
																"GL_NV_depth_buffer_float ")
											));*/
			}
			else
			{
				m_bDepthPeelingSupported = false;
			}
		}
		bool bTryMultiSample = m_bOpenGL2Supported;
		if (bTryMultiSample) // disable on old hardware
		{
			unsigned int count = 0;
			int nPixelFormat;
			// multi-sampling?
			if (g_config.multisample > 1)
			{
				int pixAttr[] =
				{
					WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
					WGL_RED_BITS_ARB, 8,                
					WGL_GREEN_BITS_ARB, 8,
					WGL_BLUE_BITS_ARB, 8,
					WGL_ALPHA_BITS_ARB, 8,
					WGL_DEPTH_BITS_ARB, 24,
					WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
					WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,       // turn multi-sampling on
					WGL_SAMPLES_ARB, g_config.multisample, // Xx multi-sampling
					0                                      // end of list
				};
				wglChoosePixelFormatARB(m_hdc, static_cast<int*>(pixAttr), NULL, 1, &nPixelFormat, &count);
			}
			else
			{
				int pixAttr[] =
				{
					WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
					WGL_RED_BITS_ARB, 8,                
					WGL_GREEN_BITS_ARB, 8,
					WGL_BLUE_BITS_ARB, 8,
					WGL_ALPHA_BITS_ARB, 8,
					WGL_DEPTH_BITS_ARB, 24,
					WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
					0                                      // end of list
				};
				wglChoosePixelFormatARB(m_hdc, static_cast<int*>(pixAttr), NULL, 1, &nPixelFormat, &count);
			}
			if (m_hglrc != NULL)
			{
				wglMakeCurrent( NULL, NULL );
				wglDeleteContext(m_hglrc);
				m_hglrc = NULL;
			}
			DestroyWindow(tempWnd);
			if (count)
			{
				m_hdc = GetDC(hwnd);
				if (!SetPixelFormat(m_hdc, nPixelFormat, NULL))
				{
					goto clean_display;
				}
				if (!((m_hglrc = wglCreateContext(m_hdc)) && wglMakeCurrent(m_hdc, m_hglrc)))
				{
					goto clean_display;
				}
				// GLEW re-init
				err = glewInit();
				if (GLEW_OK != err)
				{
					g_window.ShowErrorStatus(L"GLEW initialisation (for multisample context) failed");
					goto clean_display;
				}
			}
			else
			{
				bTryMultiSample = false;
				g_window.ShowStatus(L"Multi-sampling not supported");
			}
		}
		if (!bTryMultiSample)
		{
			m_hdc = GetDC(hwnd);
			format = ChoosePixelFormat(m_hdc,&pfd);

			if (!(SetPixelFormat(m_hdc,format,&pfd) && DescribePixelFormat(m_hdc,format,pfd.nSize,&pfd)))
			{
				goto clean_display;
			}

			if (!((m_hglrc = wglCreateContext(m_hdc)) && wglMakeCurrent(m_hdc, m_hglrc)))
			{
				goto clean_display;
			}

			// GLEW re-init
			err = glewInit();
			if (GLEW_OK != err)
			{
				g_window.ShowErrorStatus(L"GLEW initialisation (for basic context) failed");
				goto clean_display;
			}
		}
	}

	SetVSync(false);

	if (m_bDepthPeelingSupported)
	{
		if (!InitDepthPeeling())
		{
			DeleteDepthPeeling();
			m_bDepthPeelingSupported = false;
		}
	}
	if (!m_bDepthPeelingSupported)
	{
		if (m_bOpenGL2Supported)
		{
			if (!InitOtherShaders())
			{
				DeleteOtherShaders();
				m_bOpenGL2Supported = false;
			}
		}
		if (g_config.enableDepthShaders)
		{
			g_window.ShowErrorStatus(L"Your graphics hardware/driver does not support transparency by depth peeling");
		}
	}

	g_window.ShowStatus(L"OpenGL display enabled");

	UNSUSPENDPAINT;
	
    return true;

clean_display:
	if (m_hglrc) wglDeleteContext(m_hglrc);
	wglMakeCurrent(m_hdc, 0);
	ReleaseDC(m_hwnd, m_hdc);
	m_hdc = 0;
    UNSUSPENDPAINT;
	return false;
}

bool HXDisplay::SwitchDisplay(HWND hwnd, bool bLayered)
{
	if (m_bOpenGL2Supported && (m_bFBOArbSupported || m_bFBOExtSupported))
	{
		SUSPENDPAINT;
		DestroyDisplay(false);
		bool success = CreateDisplay(hwnd, bLayered);
		ResetDisplay();
		UNSUSPENDPAINT;
		return success;
	}
	else
	{
		g_window.ShowErrorStatus(L"Framebuffer support is required for transparent displays");
		return false;
	}
}

//! Reset display parameters.
void HXDisplay::ResetDisplay()
{
	SUSPENDPAINT;
    // get model's bounding box to setup the display's bounds
    HXVector3F  minVal, maxVal;
	bool gotFirstVal=false;
	//size_t mm = m_renderList.size();
	//while (mm--)
	for (size_t mm = 0; mm < m_renderList.size(); mm++)
	{
		HXRenderData* pModel = GetModel(mm);
		unsigned long numVtx = pModel->Model().modelVertexSize;
		if (numVtx)
        {
            if (!gotFirstVal)
            {
                minVal = maxVal = pModel->Model().vertexList[0];
				gotFirstVal = true;
            }
			if (pModel->Center())
			{
				for (unsigned long vv=0; vv<numVtx; ++vv)
				{
					HXVector3F vtx = pModel->Model().vertexList[vv];
					minVal[0] = (minVal[0] < vtx[0] ? minVal[0] : vtx[0]);
					minVal[1] = (minVal[1] < vtx[1] ? minVal[1] : vtx[1]);
					minVal[2] = (minVal[2] < vtx[2] ? minVal[2] : vtx[2]);
					maxVal[0] = (maxVal[0] > vtx[0] ? maxVal[0] : vtx[0]);
					maxVal[1] = (maxVal[1] > vtx[1] ? maxVal[1] : vtx[1]);
					maxVal[2] = (maxVal[2] > vtx[2] ? maxVal[2] : vtx[2]);
				}
			}
        }
	}

    m_aveVal = (minVal + maxVal) * 0.5f;
    HXVector3F diffVal = maxVal - minVal;
    m_dimMax = diffVal[0] > diffVal[1] ? diffVal[0] : diffVal[1];
    m_dimMax = m_dimMax > diffVal[2] ? m_dimMax : diffVal[2];

    // initialize display scale and translation info
    //ResetScale(g_config.resourceGroup);
    //ResetTranslation(g_config.resourceGroup);

    // setup the OpenGL projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // initialize the OpenGL model view matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0,0.0,-(m_nearZ+m_farZ)/2 * m_dimMax);

    // initialize some OpenGL rendering and lighting options
	glEnable(GL_POLYGON_OFFSET_FILL);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    GLfloat blackLight[] = {0.0,  0.0,  0.0,  1.0f};
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,blackLight);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,0);
	glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR);
    glEnable(GL_DEPTH_TEST);
    //glEnable(GL_CULL_FACE);
    //glCullFace(GL_BACK);
    glReadBuffer(GL_BACK);
	glDepthFunc(GL_LEQUAL);

	glEnable( GL_MULTISAMPLE_ARB );
    //glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB );

	// initialize lighting
	ResetLights(true);

    // build some model data to be used by OpenGL later
	BuildNewRenderData();
	BuildBackground();

    // setup the initial orientation of the model
    //ResetRotation(g_config.resourceGroup);

	glEnable(GL_LIGHTING);

	UNSUSPENDPAINT;

	g_window.ShowStatus(L"OpenGL display ready");
}

//! Change the light properties (with a HXLight object).
void HXDisplay::ChangeLight(size_t lightId, const HXLight& light)
{
	ChangeLight(lightId, light.azimuth, light.elevation, light.ambient, light.diffuse);
}

//! Change the light properties.
void HXDisplay::ChangeLight(size_t lightId, float azimuth, float elevation, float ambient, float diffuse)
{
	if (lightId < 2)
	{
		m_light[lightId].azimuth += azimuth;
		m_light[lightId].elevation += elevation;
		m_light[lightId].ambient += ambient;
		if (m_light[lightId].ambient < 0)
			m_light[lightId].ambient = 0;
		else if (m_light[lightId].ambient > 1.0f)
			m_light[lightId].ambient = 1.0f;
		m_light[lightId].diffuse += diffuse;
		if (m_light[lightId].diffuse < 0)
			m_light[lightId].diffuse = 0;
		else if (m_light[lightId].diffuse > 1.0f)
			m_light[lightId].diffuse = 1.0f;
		HXMatrix3F rm1 = RotMatrixY(m_light[lightId].azimuth);
		HXMatrix3F rm2 = RotMatrixX(m_light[lightId].elevation);
		m_lightPose[lightId] = rm1 * rm2;
	}
}

//! Initialize lighting.
void HXDisplay::ResetLights(bool loadFromConfig)
{
	if (loadFromConfig)
	{
		if (m_pConfig->light1.reset)
		{
			m_light[0].Clear();
			ChangeLight(0, m_pConfig->light1);
			m_pConfig->light1.Clear();
			m_pConfig->light1.reset = false;
		}
		else
		{
			ChangeLight(0, m_pConfig->light1);
		}
		if (m_pConfig->light2.reset)
		{
			m_light[1].Clear();
			ChangeLight(1, m_pConfig->light2);
			m_pConfig->light2.Clear();
			m_pConfig->light2.reset = false;
		}
		else
		{
			ChangeLight(0, m_pConfig->light2);
		}
	}
	else
	{
		m_light[0].Clear();
		m_light[1].Clear();
		HXLight defLight;
		defLight.Default();
		ChangeLight(0, defLight); // default lighting
		ChangeLight(1, HXLight()); // no second light
	}
}

//! Build 3D data for rendering (new models only).
void HXDisplay::BuildNewRenderData()
{
	g_window.ShowStatus(L"Building rendering data...");
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		//if (!(rdit->BuildStatus() & HXR_BUILT_NORMALS))
			rdit->BuildNormalList();
		//if (!(rdit->BuildStatus() & HXR_BUILT_TEXTURES))
			rdit->BuildTextures(true);
		/*if (m_bOpenGL2Supported) 
		{
			if (!(rdit->BuildStatus() & HXR_BUILT_SHADERS))
				rdit->BuildShaders();
		}*/
	}
	g_window.ShowStatus(L"Building rendering data... Done!");
}

//! Re-build 3D data for rendering (vertices).
void HXDisplay::BuildVertexData()
{
	#ifdef _DEBUG
	g_window.ShowStatus(L"Building vertex data for %d models...", ModelSize());
	#endif
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		rdit->BuildNormalList();
	}
	#ifdef _DEBUG
	g_window.ShowStatus(L"Building vertex data for %d models... Done!", ModelSize());
	#endif
}

//! Update 3D data for rendering (textures).
void HXDisplay::UpdateTextureData(bool bMipmap)
{
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		rdit->UpdateTextures(bMipmap);
	}
}

//! Re-build 3D data for rendering (textures).
void HXDisplay::BuildTextureData(bool bMipmap)
{
	#ifdef _DEBUG
	g_window.ShowStatus(L"Building texture data for %d models...", ModelSize());
	#endif
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		rdit->BuildTextures(bMipmap);
	}
	#ifdef _DEBUG
	g_window.ShowStatus(L"Building texture data for %d models... Done!", ModelSize());
	#endif
}

//! Re-build shaders for rendering.
void HXDisplay::BuildShaders()
{
	if (m_bOpenGL2Supported) 
	{
		#ifdef _DEBUG
		g_window.ShowStatus(L"Compiling shaders...");
		#endif
		list<HXRenderData>::iterator rdit = m_renderList.begin();
		for (; rdit != m_renderList.end(); ++rdit)
		{
			rdit->BuildShaders();
		}
		#ifdef _DEBUG
		g_window.ShowStatus(L"Compiling shaders... Done!");
		#endif
	}
	else
	{
		g_window.ShowStatus(L"Shaders are not supported by the graphics driver");
	}
}

//! Paint callback.
void HXDisplay::Paint()
{
	if (!m_bSuspendPaint)
	{
		Lock();

		// if the window size changes
		ReshapeRenderTargets();

		if (IsLayeredDisplay()) // layered window
		{
			if (m_bFBOArbSupported)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glBindFramebuffer(GL_FRAMEBUFFER, m_mainFboId);
				glDrawBuffer(GL_COLOR_ATTACHMENT0);
			}
			else // m_bFBOExtSupported
			{
				glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
				glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_mainFboId);
				glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
			}
		}
		
		glClearColor(1.0, 1.0, 1.0, 1.0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		// resize the OpenGL viewport
		int x[4];
		glGetIntegerv(GL_VIEWPORT, x);
		glViewport(x[0], x[1], m_width, m_height);

		// lights
		for (int l = 0; l < 2; l++) // max 2 lights
		{
			//if (m_light[l].ambient || m_light[l].diffuse)
			{
				glEnable(GL_LIGHT0+l);
				glPushMatrix();
				{
					// Rotation
					HXMatrix4F mvm;
					mvm.SetIdentity();
					for (int i=0; i<3; i++)
					{
						for (int j=0; j<3; j++)
						{
							mvm[i][j] = m_lightPose[l][j][i];
						}
					}
					glMultMatrixf(&mvm[0][0]);
					// Position
					GLfloat pos[] = {0.0f,0.0f,1.0f,0.0f};
					glLightfv(GL_LIGHT0+l, GL_POSITION, pos);
					// Ambient brightness
					GLfloat colour[] = {m_light[l].ambient, m_light[l].ambient, m_light[l].ambient, 1.0f};
					glLightfv(GL_LIGHT0+l, GL_AMBIENT, colour);
					// Diffuse and specular brightness
					GLfloat diff_colour[] = {m_light[l].diffuse, m_light[l].diffuse, m_light[l].diffuse, 1.0f};
					glLightfv(GL_LIGHT0+l, GL_DIFFUSE, diff_colour);
					GLfloat spec_colour[] = {m_light[l].diffuse, m_light[l].diffuse, m_light[l].diffuse, 1.0f};
					glLightfv(GL_LIGHT0+l, GL_SPECULAR, spec_colour);
				}
				glPopMatrix();
			}
			/*else
			{
				glDisable(GL_LIGHT0+l);
			}*/
		}

		if (g_config.bUseShaders && m_bOpenGL2Supported)
		{
			if (m_bDepthPeelingSupported && (g_config.maxDepthPasses > 1))
			{
				DrawWithDepth();
			}
			else
			{
				DrawWithShaders();
			}
		}
		else
		{
			DrawLegacy();
		}
		
		if (m_pFont)
		{
			
			glMatrixMode(GL_PROJECTION);
			glPushMatrix();
			glLoadIdentity();
			gluPerspective(45.0f, m_width / (float)m_height, -1, 0);
			//gluPerspective(45.0f, (m_aspect * m_width) / m_height, m_dimMax * m_nearZ - m_pConfig->subtitleZ, m_dimMax * m_farZ - m_pConfig->subtitleZ);
			//glOrtho(0, 1, 0, 1, 0, 1);
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			glDisable(GL_TEXTURE_2D);
			glDisable(GL_LIGHTING);
			FTBBox box = m_pFont->BBox(g_subtitle.GetText(), g_subtitle.Length());
			float width = static_cast<float>(box.Upper().X() - box.Lower().X());
			//float height = static_cast<float>(box.Upper().Y() - box.Lower().Y());
			glTranslatef(0.0f, 0.0f, m_pConfig->subtitleZ);
			//glTranslatef(0.0f, 0.0f,0.0f);
			glTranslatef(m_pConfig->subtitleX-(width/2), m_pConfig->subtitleY, 0.0f);
			glColor3f(m_pConfig->subtitleRedness, m_pConfig->subtitleGreenness, m_pConfig->subtitleBlueness);
			m_pFont->Render(g_subtitle.GetText());
			glColor3f(1.0f, 1.0f, 1.0f);
			glEnable(GL_TEXTURE_2D);
			glPopMatrix();
			glPopMatrix();
		}

		if (IsLayeredDisplay()) // layered window
		{
			if (m_bFBOArbSupported)
			{
				glReadBuffer((GLenum)GL_COLOR_ATTACHMENT0);
				//glPixelStorei(GL_PACK_ALIGNMENT, 1);
				glReadPixels(0, 0, m_pImage->width, m_pImage->height, GL_BGRA, GL_UNSIGNED_BYTE, m_pImage->pTempPixels);
				glBindFramebuffer(GL_FRAMEBUFFER, 0);
			}
			else // m_bFBOExtSupported
			{
				glReadBuffer((GLenum)GL_COLOR_ATTACHMENT0_EXT);
				glReadPixels(0, 0, m_pImage->width, m_pImage->height, GL_BGRA, GL_UNSIGNED_BYTE, m_pImage->pTempPixels);
				glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
			}

			for (int i = 0; i < m_pImage->height; ++i)
			{
				memcpy(&m_pImage->pPixels[m_pImage->pitch * i],
					&m_pImage->pTempPixels[((m_pImage->height - 1) - i) * (m_pImage->width * 4)],
					m_pImage->width * 4);
			}
			// Pre-multiply each pixel with its alpha component
			m_pImage->PreMultAlpha();

			// Update layered window
			RedrawLayeredWindow();
		}
		else // normal window
		{
			SwapBuffers(m_hdc);
		}
		
		Unlock();
	}
}

void HXDisplay::RedrawLayeredWindow()
{
    // The call to UpdateLayeredWindow() is what makes a non-rectangular
    // window possible. To enable per pixel alpha blending we pass in the
    // argument ULW_ALPHA, and provide a BLENDFUNCTION structure filled in
    // to do per pixel alpha blending.

    HDC hdc = GetDC(m_hwnd);

    if (hdc)
    {
        HGDIOBJ hPrevObj = 0;
        POINT ptDest = {0, 0};
        POINT ptSrc = {0, 0};
        SIZE client = {m_pImage->width, m_pImage->height};
        BLENDFUNCTION blendFunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};

        hPrevObj = SelectObject(m_pImage->hdc, m_pImage->hBitmap);
        ClientToScreen(m_hwnd, &ptDest);

        UpdateLayeredWindow(m_hwnd, hdc, NULL, &client, m_pImage->hdc, &ptSrc, 0, &blendFunc, ULW_ALPHA);

        SelectObject(m_pImage->hdc, hPrevObj);
        ReleaseDC(m_hwnd, hdc);
    }
}

//****************************************************************************

// Resize 3D rendering display.
void HXDisplay::SetSize(int cx, int cy)
{
	m_width = cx;
	m_height = cy;
}

//! Rotate 3D models.
void HXDisplay::Rotate(HXResourceGroup& rg, float delX, float delY, float delZ)
{
	rg.transform.rotX += delX;
	rg.transform.rotY += delY;
	rg.transform.rotZ += delZ;
	rg.transform.mvmIsCurrent = false;
	/*HXMatrix3F rm1 = RotMatrixY(g_config.resourceGroup.transform.rotX);
	HXMatrix3F rm2 = RotMatrixX(g_config.resourceGroup.transform.rotY);
	HXMatrix3F rm3 = RotMatrixZ(g_config.resourceGroup.transform.rotZ);
	m_pose = rm1 * rm2 * rm3;*/
	
	/*HXMatrix3F rm1 = RotMatrixY(delX / 200.0f);
	HXMatrix3F rm2 = RotMatrixX(delY / 200.0f);
	m_pose = rm2 * rm1 * m_pose;
	*/
}

//! Reset rotation to identity/frontal.
void HXDisplay::ResetRotation(HXResourceGroup& rg)
{
	rg.transform.rotX = 0;
	rg.transform.rotY = 0;
	rg.transform.rotZ = 0;
	rg.transform.mvmIsCurrent = false;
	//m_pose.SetIdentity();
}

//! Scale 3D models.
void HXDisplay::Scale(HXResourceGroup& rg, float delX, float delY, float delZ)
{
	rg.transform.scaleX += delX;
	rg.transform.scaleY += delY;
	rg.transform.scaleZ += delZ;
	rg.transform.mvmIsCurrent = false;
}

//! Reset scale to default.
void HXDisplay::ResetScale(HXResourceGroup& rg)
{
	rg.transform.scaleX = 1.0f;
	rg.transform.scaleY = 1.0f;
	rg.transform.scaleZ = 1.0f;
	rg.transform.mvmIsCurrent = false;
}

//! Translate 3D models.
void HXDisplay::Translate(HXResourceGroup& rg, float delX, float delY, float delZ)
{
	rg.transform.transX += delX;
	rg.transform.transY += delY;
	rg.transform.transZ += delZ;
	/*if (rg.transform.transZ > (m_dimMax * (m_nearZ-0.001f)))
		rg.transform.transZ = m_dimMax * (m_nearZ-0.001f);
	else
	if (rg.transform.transZ < -(m_dimMax * (m_farZ+0.001f)))
		rg.transform.transZ = -m_dimMax * (m_farZ+0.001f);*/
	MoveSubtitlePosition();
	rg.transform.mvmIsCurrent = false;
}

//! Center 3D models.
void HXDisplay::ResetTranslation(HXResourceGroup& rg)
{
	rg.transform.transX = 0;
	rg.transform.transY = 0;
	rg.transform.transZ = 0;
	MoveSubtitlePosition();
	rg.transform.mvmIsCurrent = false;
}

//! Change the position of the subtitle/
void HXDisplay::MoveSubtitlePosition(float x, float y, float z)
{
	if (m_pConfig)
	{
		m_pConfig->subtitleX += x;
		m_pConfig->subtitleY += y;
		m_pConfig->subtitleZ += z;

		// subtitle must remain in drawing volume
		GLdouble nearPlane = m_dimMax * m_nearZ -g_config.resourceGroup.transform.transZ;
		GLdouble farPlane = m_dimMax * m_farZ - g_config.resourceGroup.transform.transZ;
		GLdouble midPlane = (nearPlane + farPlane)/2;
		if (midPlane+m_pConfig->subtitleZ > farPlane)
		{
			//m_pConfig->subtitleZ = static_cast<float>(farPlane - midPlane - 0.01);
		}
		else
		if (midPlane+m_pConfig->subtitleZ < nearPlane)
		{
			//m_pConfig->subtitleZ = static_cast<float>(nearPlane - midPlane + 0.01);
		}
	}
}

//! Reset the position of the subtitle to internal defaults.
void HXDisplay::ResetSubtitlePosition()
{
	m_pConfig->subtitleX = SUBTITLE_DEFAULT_XPOS;
	m_pConfig->subtitleY = SUBTITLE_DEFAULT_YPOS;
	m_pConfig->subtitleZ = SUBTITLE_DEFAULT_ZPOS;
}

//! Copy current camera and lighting parameters.
void HXDisplay::ReadCameraLights(float* pBuffer)
{
	pBuffer[0] = g_config.resourceGroup.transform.rotX;
	pBuffer[1] = g_config.resourceGroup.transform.rotY;
	pBuffer[2] = g_config.resourceGroup.transform.rotZ;
	pBuffer[3] = g_config.resourceGroup.transform.transX;
	pBuffer[4] = g_config.resourceGroup.transform.transY;
	pBuffer[5] = g_config.resourceGroup.transform.transZ; 
	pBuffer[6] = g_config.resourceGroup.transform.scaleX;
	pBuffer[7] = g_config.resourceGroup.transform.scaleY;
	pBuffer[8] = g_config.resourceGroup.transform.scaleZ;
	pBuffer[10] = m_light[0].azimuth;
	pBuffer[11] = m_light[0].elevation;
	pBuffer[12] = m_light[0].ambient;
	pBuffer[13] = m_light[0].diffuse;
	pBuffer[20] = m_light[1].azimuth;
	pBuffer[21] = m_light[1].elevation;
	pBuffer[22] = m_light[1].ambient;
	pBuffer[23] = m_light[1].diffuse;
}

//! Change current camera and lighting parameters by passed array.
void HXDisplay::ChangeCameraLights(float* pBuffer)
{
	Rotate(g_config.resourceGroup, pBuffer[0], pBuffer[1], pBuffer[2]);
	Translate(g_config.resourceGroup, pBuffer[3], pBuffer[4], pBuffer[5]);
	Scale(g_config.resourceGroup, pBuffer[6], pBuffer[7], pBuffer[8]);
	ChangeLight(0, pBuffer[10], pBuffer[11], pBuffer[12], pBuffer[13]);
	ChangeLight(1, pBuffer[20], pBuffer[21], pBuffer[22], pBuffer[23]);
}

//****************************************************************************

//! Show the current camera parameters in the window status bar.
void HXDisplay::CameraStatus()
{
	g_window.ShowStatus(L"Rotation: x=%.2f y=%.2f z=%.2f | Translation: x=%.0f y=%.0f z=%.0f | Scale: x=%.2f y=%.2f z=%.2f (FOV=%.1f)", g_config.resourceGroup.transform.rotX, g_config.resourceGroup.transform.rotY, g_config.resourceGroup.transform.rotZ, g_config.resourceGroup.transform.transX, g_config.resourceGroup.transform.transY, g_config.resourceGroup.transform.transZ, g_config.resourceGroup.transform.scaleX, g_config.resourceGroup.transform.scaleY, g_config.resourceGroup.transform.scaleX, g_config.fov);
}

//! Show the current lighting parameters in the window status bar.
void HXDisplay::LightStatus()
{
	if (m_light[1].ambient || m_light[1].diffuse)
		g_window.ShowStatus(L"Light 1: azimuth=%.2f elevation=%.2f ambient=%.2f diffuse=%.2f (2: azimuth=%.2f elevation=%.2f ambient=%.2f diffuse=%.2f)", m_light[0].azimuth, m_light[0].elevation, m_light[0].ambient, m_light[0].diffuse, m_light[1].azimuth, m_light[1].elevation, m_light[1].ambient, m_light[1].diffuse);
	else
		g_window.ShowStatus(L"Light 1: azimuth=%.2f elevation=%.2f ambient=%.2f diffuse=%.2f", m_light[0].azimuth, m_light[0].elevation, m_light[0].ambient, m_light[0].diffuse);
}

//! Show the current subtitle display parameters in the window status bar.
void HXDisplay::SubtitleStatus()
{
	g_window.ShowStatus(L"Subtitle: x=%.0f y=%.0f z=%.0f", m_pConfig->subtitleX, m_pConfig->subtitleY, m_pConfig->subtitleZ);
}

//****************************************************************************

//! Get morph data for the specified model index.
HXMorph* HXDisplay::GetMorph(size_t idx)
{
	if (idx < ModelSize())
		return &GetModel(idx)->Morphs();
	else
		return NULL;
}

void HXDisplay::GetMorphLabels(vector<wstring>& labelArray)
{
	map<wstring, HXMorphData>::iterator mmIt = m_morphData.begin();
	for (; mmIt != m_morphData.end(); ++mmIt)
	{
		labelArray.push_back(mmIt->first);
	}
}

//! Reset all morphs.
void HXDisplay::ResetAllMorphs()
{
	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		rdit->Morphs().Reset();
	}
}

//! Cache pointers to the morph values for all models currently loaded.
void HXDisplay::CreateMorphCache()
{
	HXMorph* pMorph;
	size_t index = 0;
	while (pMorph = GetMorph(index++))
	{
		map<wstring, HXMorphIndex>::iterator mmIt = pMorph->morphMap.begin();
		for (; mmIt != pMorph->morphMap.end(); ++mmIt)
		{
			switch (mmIt->second.source)
			{
			case HXMT_DIFF:
				m_morphData[mmIt->first].pointerCache.push_back(&pMorph->diffMorphVals[mmIt->second.index]);
				break;
			case HXMT_STAT:
				m_morphData[mmIt->first].pointerCache.push_back(&pMorph->statMorphVals[mmIt->second.index]);
				break;
			default:
				break;
			}
		}
	}
}

//! Clear the morph value pointer cache.
void HXDisplay::ClearMorphCache()
{
	if (m_pAlive)
		m_pAlive->Clear();
	m_morphData.clear();
}

//! Set current morph targets to zero.
void HXDisplay::ResetMorphValues(HXMorphStream driver)
{
	Lock();
	map<wstring, HXMorphData>::iterator mdIt = m_morphData.begin();
	for (; mdIt != m_morphData.end(); ++mdIt)
	{
		switch (driver)
		{
			case HXMS_EMOTE:
				mdIt->second.emote = 0;
				break;
			case HXMS_IDLE:
				mdIt->second.idle = 0;
				break;
			case HXMS_SPEECH:
				mdIt->second.speech = 0;
				break;
			default:
				mdIt->second.other = 0;
				break;
		}
	}
	Unlock();
}

//! Set the value of a string-labelled morph in the buffer for a particular morph driver.
void HXDisplay::SetMorphValue(HXMorphStream driver, const wstring& label, float value)
{
	Lock();
	switch (driver)
	{
		case HXMS_EMOTE:
			m_morphData[label].emote = value;
			break;
		case HXMS_IDLE:
			m_morphData[label].idle = value;
			break;
		case HXMS_SPEECH:
			m_morphData[label].speech = value;
			break;
		default:
			m_morphData[label].other = value;
			break;
	}
	Unlock();
}

//! Like SetMorphValue(), but values are relative to existing value.
void HXDisplay::ChangeMorphValue(HXMorphStream driver, const wstring& label, float value)
{
	Lock();
	switch (driver)
	{
		case HXMS_EMOTE:
			m_morphData[label].emote += value;
			break;
		case HXMS_IDLE:
			m_morphData[label].idle += value;
			break;
		case HXMS_SPEECH:
			m_morphData[label].speech += value;
			break;
		default:
			m_morphData[label].other += value;
			break;
	}
	Unlock();
}

//! Change current morphs by passed array.
void HXDisplay::GetRemoteMorphs(float* pBuffer, size_t max_size)
{
	Lock();
	map<wstring, HXMorphData>::iterator mdIt = m_morphData.begin();
	for (size_t m = 0; (mdIt != m_morphData.end()) && (m < max_size); ++mdIt, m++)
	{
		//pBuffer[m] = mdIt->second.other;
		pBuffer[m] = mdIt->second.GetAmplitude();
	}
	Unlock();
}

//! Change current morphs by passed array.
void HXDisplay::ChangeRemoteMorphs(float* pBuffer, size_t max_size)
{
	Lock();
	map<wstring, HXMorphData>::iterator mdIt = m_morphData.begin();
	for (size_t m = 0; (mdIt != m_morphData.end()) && (m < max_size); ++mdIt, m++)
	{
		mdIt->second.other += pBuffer[m];
	}
	Unlock();
}

//! Reset changed morphs.
void HXDisplay::ResetRemoteMorphs()
{
	Lock();
	map<wstring, HXMorphData>::iterator mdIt = m_morphData.begin();
	for (size_t m = 0; mdIt != m_morphData.end(); ++mdIt, m++)
	{
		mdIt->second.other = 0;
	}
	Unlock();
}

//! Update 3D display with current morph values.
void HXDisplay::UpdateMorphs()
{
	Lock();
	#ifdef _MORPHDEBUG
		if (g_config.morphDumpFile.size()) 
		{
			FILE* streamOut;
			if (_wfopen_s(&streamOut, g_config.morphDumpFile.c_str(), L"a+") == 0)
			{
				map<wstring, HXMorphData>::iterator moit = m_morphData.begin();
				for (; moit != m_morphData.end(); ++moit)
				{
					fwprintf(streamOut, L"%f ", moit->second.GetAmplitude());
				}
				fwprintf(streamOut, L"\n");
				fclose(streamOut);
			}
		}
	#endif

	map<wstring, HXMorphData>::iterator moit = m_morphData.begin();
	for (size_t i = 0; moit != m_morphData.end(); ++moit, i++)
	{
		float amp = moit->second.GetAmplitude();
		list<float*>::iterator rlit = moit->second.pointerCache.begin();
		for (; rlit != moit->second.pointerCache.end(); ++rlit)
		{
			**rlit = amp;
		}
	}

	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
		rdit->ApplyMorphs();
	}

	Unlock();
}

//****************************************************************************

bool HXDisplay::InitDepthPeeling()
{
	m_drawBuffers[0] = GL_COLOR_ATTACHMENT0;
	m_drawBuffers[1] = GL_COLOR_ATTACHMENT1;
	m_drawBuffers[2] = GL_COLOR_ATTACHMENT2;
	m_drawBuffers[3] = GL_COLOR_ATTACHMENT3;
	m_drawBuffers[4] = GL_COLOR_ATTACHMENT4;
	m_drawBuffers[5] = GL_COLOR_ATTACHMENT5;
	m_drawBuffers[6] = GL_COLOR_ATTACHMENT6;

	m_opacity = 0.5f;
	m_imageWidth = m_width ? m_width : 1024;
	m_imageHeight = m_height ? m_height : 768;

	bool loaded = true;

	if (IsLayeredDisplay())
	{
		// use front peeling
		InitFrontPeelingRenderTargets();
		loaded &= InitFrontPeelingShaders();
	}
	else
	{
		// use dual peeling
		InitDualPeelingRenderTargets();
		loaded &= InitDualPeelingShaders();
	}
	loaded &= InitOtherShaders();

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	if (loaded)
	{
		MakeFullScreenQuad(m_quadDisplayList);
		glGenQueries(1, &m_queryId);
		return true;
	}
	return false;
}

bool HXDisplay::InitFrontPeelingShaders()
{
	bool loaded = true;

	loaded &= m_shaderFrontPeel.AttachVertexShaderFromFile(g_config.shaderDir + L"\\shade_vertex.glsl");
	loaded &= m_shaderFrontPeel.AttachVertexShaderFromFile(g_config.shaderDir + L"\\front_peeling_peel_vertex.glsl");
	loaded &= m_shaderFrontPeel.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment.glsl");
	loaded &= m_shaderFrontPeel.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\front_peeling_peel_fragment.glsl");
	loaded &= m_shaderFrontPeel.Link();

	loaded &= m_shaderFrontInit.AttachVertexShaderFromFile(g_config.shaderDir + L"\\shade_vertex.glsl");
	loaded &= m_shaderFrontInit.AttachVertexShaderFromFile(g_config.shaderDir + L"\\front_peeling_init_vertex.glsl");
	loaded &= m_shaderFrontInit.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment.glsl");
	loaded &= m_shaderFrontInit.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\front_peeling_init_fragment.glsl");
	loaded &= m_shaderFrontInit.Link();

	loaded &= m_shaderFrontBlend.AttachVertexShaderFromFile(g_config.shaderDir + L"\\front_peeling_blend_vertex.glsl");
	loaded &= m_shaderFrontBlend.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\front_peeling_blend_fragment.glsl");
	loaded &= m_shaderFrontBlend.Link();

	loaded &= m_shaderFrontFinal.AttachVertexShaderFromFile(g_config.shaderDir + L"\\front_peeling_final_vertex.glsl");
	loaded &= m_shaderFrontFinal.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\front_peeling_final_fragment.glsl");
	loaded &= m_shaderFrontFinal.Link();

	return loaded;
}

bool HXDisplay::InitDualPeelingShaders()
{
	bool loaded = true;

	loaded &= m_shaderDualInit.AttachVertexShaderFromFile(g_config.shaderDir + L"\\dual_peeling_init_vertex.glsl");
	loaded &= m_shaderDualInit.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\dual_peeling_init_fragment.glsl");
	loaded &= m_shaderDualInit.Link();

	loaded &= m_shaderDualPeel.AttachVertexShaderFromFile(g_config.shaderDir + L"\\shade_vertex.glsl");
	loaded &= m_shaderDualPeel.AttachVertexShaderFromFile(g_config.shaderDir + L"\\dual_peeling_peel_vertex.glsl");
	loaded &= m_shaderDualPeel.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment.glsl");
	loaded &= m_shaderDualPeel.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\dual_peeling_peel_fragment.glsl");
	loaded &= m_shaderDualPeel.Link();

	loaded &= m_shaderDualBlend.AttachVertexShaderFromFile(g_config.shaderDir + L"\\dual_peeling_blend_vertex.glsl");
	loaded &= m_shaderDualBlend.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\dual_peeling_blend_fragment.glsl");
	loaded &= m_shaderDualBlend.Link();

	loaded &= m_shaderDualFinal.AttachVertexShaderFromFile(g_config.shaderDir + L"\\dual_peeling_final_vertex.glsl");
	loaded &= m_shaderDualFinal.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\dual_peeling_final_fragment.glsl");
	loaded &= m_shaderDualFinal.Link();

	return loaded;
}

bool HXDisplay::InitOtherShaders()
{
	bool loaded = true;

	loaded &= m_shaderPhong.AttachVertexShaderFromFile(g_config.shaderDir + L"\\shade_vertex.glsl");
	loaded &= m_shaderPhong.AttachVertexShaderFromFile(g_config.shaderDir + L"\\dual_peeling_peel_vertex.glsl");
	loaded &= m_shaderPhong.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment.glsl");
	loaded &= m_shaderPhong.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment_phong.glsl");
	loaded &= m_shaderPhong.Link();

	loaded &= m_shaderSimple.AttachFragmentShaderFromFile(g_config.shaderDir + L"\\shade_fragment_simple.glsl");
	loaded &= m_shaderSimple.Link();

	return loaded;
}

void HXDisplay::InitMainFBO()
{
	if (!m_pImage && (m_bFBOArbSupported || m_bFBOExtSupported))
	{
		if (m_bFBOArbSupported)
		{
			glGenFramebuffers(1, &m_mainFboId);
			glBindFramebuffer(GL_FRAMEBUFFER, m_mainFboId);
			
			glGenRenderbuffers(1, &m_mainDepthTexId);
			glBindRenderbuffer(GL_RENDERBUFFER, m_mainDepthTexId);
			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, m_imageWidth, m_imageHeight);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_mainDepthTexId);

			glGenTextures(1, &m_mainColorTexId);
			glBindTexture(GL_TEXTURE_2D, m_mainColorTexId);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_imageWidth, m_imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_mainColorTexId, 0);
		
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
		}
		else // m_bFBOExtSupported
		{
			glGenFramebuffersEXT(1, &m_mainFboId);
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_mainFboId);
			
			glGenRenderbuffersEXT(1, &m_mainDepthTexId);
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_mainDepthTexId);
			glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, m_imageWidth, m_imageHeight);
			glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_mainDepthTexId);

			glGenTextures(1, &m_mainColorTexId);
			glBindTexture(GL_TEXTURE_2D, m_mainColorTexId);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_imageWidth, m_imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_mainColorTexId, 0);
		
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		}

		m_pImage = new HXBufferImage();
		m_pImage->Create(m_imageWidth, m_imageHeight);
	}
}

void HXDisplay::InitDualPeelingRenderTargets()
{
	glGenTextures(2, m_dualDepthTexId);
	glGenTextures(2, m_dualFrontBlenderTexId);
	glGenTextures(2, m_dualBackTempTexId);
	glGenFramebuffers(1, &m_dualPeelingSingleFboId);
	for (int i = 0; i < 2; i++)
	{
		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_dualDepthTexId[i]);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_FLOAT_RG32_NV, m_imageWidth, m_imageHeight,
					 0, GL_RGB, GL_FLOAT, 0);

		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_dualFrontBlenderTexId[i]);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, m_imageWidth, m_imageHeight,
					 0, GL_RGBA, GL_FLOAT, 0);

		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_dualBackTempTexId[i]);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, m_imageWidth, m_imageHeight,
					 0, GL_RGBA, GL_FLOAT, 0);
	}

	glGenTextures(1, &m_dualBackBlenderTexId);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_dualBackBlenderTexId);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, m_imageWidth, m_imageHeight, 0, GL_RGB, GL_FLOAT, 0);

	glBindFramebuffer(GL_FRAMEBUFFER, m_dualPeelingSingleFboId);

	int j = 0;
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualDepthTexId[j], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualFrontBlenderTexId[j], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualBackTempTexId[j], 0);

	j = 1;
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualDepthTexId[j], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualFrontBlenderTexId[j], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5,
								  GL_TEXTURE_RECTANGLE_ARB, m_dualBackTempTexId[j], 0);

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6,
							  GL_TEXTURE_RECTANGLE_ARB, m_dualBackBlenderTexId, 0);

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void HXDisplay::InitFrontPeelingRenderTargets()
{
	glGenTextures(2, m_frontDepthTexId);
	glGenTextures(2, m_frontColorTexId);
	glGenFramebuffersEXT(2, m_frontFboId);

	for (int i = 0; i < 2; i++)
	{
		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_frontDepthTexId[i]);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT32F_NV,
					 m_imageWidth, m_imageHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_frontColorTexId[i]);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, m_imageWidth, m_imageHeight,
					 0, GL_RGBA, GL_FLOAT, 0);

		glBindFramebuffer(GL_FRAMEBUFFER, m_frontFboId[i]);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
								  GL_TEXTURE_RECTANGLE_ARB, m_frontDepthTexId[i], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
								  GL_TEXTURE_RECTANGLE_ARB, m_frontColorTexId[i], 0);
	}

	glGenTextures(1, &m_frontColorBlenderTexId);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_frontColorBlenderTexId);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, m_imageWidth, m_imageHeight,
				 0, GL_RGBA, GL_FLOAT, 0);

	glGenFramebuffers(1, &m_frontColorBlenderFboId);
	glBindFramebuffer(GL_FRAMEBUFFER, m_frontColorBlenderFboId);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
							  GL_TEXTURE_RECTANGLE_ARB, m_frontDepthTexId[0], 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
							  GL_TEXTURE_RECTANGLE_ARB, m_frontColorBlenderTexId, 0);

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void HXDisplay::MakeFullScreenQuad(GLuint& quad)
{
	quad = glGenLists(1);
	glNewList(quad, GL_COMPILE);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0.0, 1.0, 0.0, 1.0);
	glBegin(GL_QUADS);
	{
		glVertex2f(0.0, 0.0); 
		glVertex2f(1.0, 0.0);
		glVertex2f(1.0, 1.0);
		glVertex2f(0.0, 1.0);
	}
	glEnd();
	glPopMatrix();

	glEndList();
}

void HXDisplay::DeleteDepthPeeling()
{
	// remove shaders and render targets
	if (IsLayeredDisplay())
	{
		DeleteFrontPeelingShaders();
		DeleteFrontPeelingRenderTargets();
	}
	else
	{
		DeleteDualPeelingShaders();
		DeleteDualPeelingRenderTargets();
	}
	DeleteOtherShaders();
	
	glDeleteLists(m_quadDisplayList, 1); // not necessary?
	glDeleteQueries(1, &m_queryId); // not necessary?
}

void HXDisplay::DeleteFrontPeelingShaders()
{
	m_shaderFrontInit.Destroy();
	m_shaderFrontPeel.Destroy();
	m_shaderFrontBlend.Destroy();
	m_shaderFrontFinal.Destroy();
}

void HXDisplay::DeleteDualPeelingShaders()
{
	m_shaderDualInit.Destroy();
	m_shaderDualPeel.Destroy();
	m_shaderDualBlend.Destroy();
	m_shaderDualFinal.Destroy();
}

void HXDisplay::DeleteOtherShaders()
{
	m_shaderPhong.Destroy();
	m_shaderSimple.Destroy();
}

void HXDisplay::DeleteMainFBO()
{
	if (IsLayeredDisplay())
	{
		if (m_bFBOArbSupported)
		{
			glDeleteFramebuffers(1, &m_mainFboId);
			glDeleteTextures(1, &m_mainColorTexId);
			glDeleteRenderbuffers(1, &m_mainDepthTexId);
		}
		else // m_bFBOExtSupported
		{
			glDeleteFramebuffersEXT(1, &m_mainFboId);
			glDeleteTextures(1, &m_mainColorTexId);
			glDeleteRenderbuffersEXT(1, &m_mainDepthTexId);
		}
		m_pImage->Destroy();
		m_pImage = NULL;
	}
}

void HXDisplay::DeleteFrontPeelingRenderTargets()
{
	if (m_frontColorBlenderFboId)
	{
		glDeleteFramebuffers(2, m_frontFboId);
		glDeleteFramebuffers(1, &m_frontColorBlenderFboId);
		m_frontColorBlenderFboId = 0;
		glDeleteTextures(2, m_frontDepthTexId);
		glDeleteTextures(2, m_frontColorTexId);
		glDeleteTextures(1, &m_frontColorBlenderTexId);
	}
}

void HXDisplay::DeleteDualPeelingRenderTargets()
{
	if (m_dualPeelingSingleFboId)
	{
		glDeleteFramebuffers(1, &m_dualBackBlenderFboId);
		glDeleteFramebuffers(1, &m_dualPeelingSingleFboId);
		m_dualPeelingSingleFboId = 0;
		glDeleteTextures(2, m_dualDepthTexId);
		glDeleteTextures(2, m_dualFrontBlenderTexId);
		glDeleteTextures(2, m_dualBackTempTexId);
		glDeleteTextures(1, &m_dualBackBlenderTexId);
	}
}

void HXDisplay::ReshapeRenderTargets()
{
	if (m_imageWidth != m_width || m_imageHeight != m_height)
	{
		m_imageWidth = m_width;
		m_imageHeight = m_height;

		if (m_bDepthPeelingSupported && (g_config.maxDepthPasses > 1))
		{
			if (IsLayeredDisplay())
			{
				// use front peeler
				DeleteFrontPeelingRenderTargets();
				InitFrontPeelingRenderTargets();
			}
			else
			{
				// use dual peeler
				DeleteDualPeelingRenderTargets();
				InitDualPeelingRenderTargets();
			}
		}

		if (IsLayeredDisplay()) // draw to FBO/image
		{
			DeleteMainFBO();
			InitMainFBO();
		}
	}
}

// Gouraud shading only
void HXDisplay::DrawLegacy()
{
	glDisable(GL_LIGHTING);
	glDepthMask(GL_FALSE);
	glClearColor(g_config.bgRedness, g_config.bgGreenness, g_config.bgBlueness, 0);
	glClear(GL_COLOR_BUFFER_BIT);
	DrawBackground(NULL);
	glDepthMask(GL_TRUE);
	glEnable(GL_LIGHTING);
	StartProjection();
	RecursiveDraw(g_config.resourceGroup, HXDRAW_LEGACY);
}

// Phong shading only
void HXDisplay::DrawWithShaders()
{
	if (m_bFBOArbSupported)
	{
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		if (IsLayeredDisplay())
		{
			glBindFramebuffer(GL_FRAMEBUFFER, m_mainFboId);
			glDrawBuffer(GL_COLOR_ATTACHMENT0);
		}
		else
		{
			glDrawBuffer(GL_BACK);
		}
	}
	else
	if (m_bFBOExtSupported)
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		if (IsLayeredDisplay())
		{
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_mainFboId);
			glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
		}
		else
		{
			glDrawBuffer(GL_BACK);
		}
	}
	glDepthMask(GL_FALSE);
	glClearColor(g_config.bgRedness, g_config.bgGreenness, g_config.bgBlueness, 0);
	glClear(GL_COLOR_BUFFER_BIT);
	DrawBackground(&m_shaderSimple);
	glDepthMask(GL_TRUE);
	StartProjection();
	m_shaderPhong.Bind();
	RecursiveDraw(g_config.resourceGroup, HXDRAW_SHADERS);
	m_shaderPhong.Unbind();
}

// Dual or front depth peeling
void HXDisplay::DrawWithDepth()
{
	const float MAX_DEPTH = 1.0;

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	if (IsLayeredDisplay())
	{
		// FRONT DEPTH PEELING (for transparent window use only)

		// ---------------------------------------------------------------------
		// 1. Initialize Min Depth Buffer
		// ---------------------------------------------------------------------

		glBindFramebuffer(GL_FRAMEBUFFER, m_frontColorBlenderFboId);
		glDrawBuffer(m_drawBuffers[0]);

		glClearColor(0, 0, 0, 1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glEnable(GL_DEPTH_TEST);

		StartProjection();

		m_shaderFrontInit.Bind();
		m_shaderFrontInit.SetUniform("Alpha", (float*)&m_opacity, 1);
		RecursiveDraw(g_config.resourceGroup, HXDRAW_FRONT_INIT);
		m_shaderFrontInit.Unbind();

		// ---------------------------------------------------------------------
		// 2. Depth Peeling + Blending
		// ---------------------------------------------------------------------

		int numLayers = (g_config.maxDepthPasses - 1) * 2;
		bool useOQ = true;
		for (int layer = 1; useOQ || layer < numLayers; layer++) {
			int currId = layer % 2;
			int prevId = 1 - currId;

			glBindFramebuffer(GL_FRAMEBUFFER, m_frontFboId[currId]);
			glDrawBuffer(m_drawBuffers[0]);

			glClearColor(0, 0, 0, 0);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			glDisable(GL_BLEND);
			glEnable(GL_DEPTH_TEST);

			if (useOQ) {
				glBeginQuery(GL_SAMPLES_PASSED_ARB, m_queryId);
			}

			m_shaderFrontPeel.Bind();
			m_shaderFrontPeel.BindTextureRECT("DepthTex", m_frontDepthTexId[prevId], 0);
			m_shaderFrontPeel.SetUniform("Alpha", (float*)&m_opacity, 1);
			RecursiveDraw(g_config.resourceGroup, HXDRAW_FRONT_PEEL);
			m_shaderFrontPeel.Unbind();

			if (useOQ) {
				glEndQuery(GL_SAMPLES_PASSED_ARB);
			}

			glBindFramebuffer(GL_FRAMEBUFFER, m_frontColorBlenderFboId);
			glDrawBuffer(m_drawBuffers[0]);

			glDisable(GL_DEPTH_TEST);
			glEnable(GL_BLEND);

			glBlendEquation(GL_FUNC_ADD);
			glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE,
								GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
			
			m_shaderFrontBlend.Bind();
			m_shaderFrontBlend.BindTextureRECT("TempTex", m_frontColorTexId[currId], 0);
			glCallList(m_quadDisplayList);
			m_shaderFrontBlend.Unbind();

			glDisable(GL_BLEND);

			if (useOQ) {
				GLuint sample_count;
				glGetQueryObjectuiv(m_queryId, GL_QUERY_RESULT_ARB, &sample_count);
				if (sample_count == 0) {
					break;
				}
			}
		}

		// ---------------------------------------------------------------------
		// 3. Final Pass
		// ---------------------------------------------------------------------

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glBindFramebuffer(GL_FRAMEBUFFER, m_mainFboId);
		glDrawBuffer(GL_COLOR_ATTACHMENT0);
	
		m_shaderFrontFinal.Bind();
		//m_shaderFrontFinal.setUniform("BackgroundColor", m_backgroundColor, 3);
		m_shaderFrontFinal.BindTextureRECT("ColorTex", m_frontColorBlenderTexId, 0);
		glCallList(m_quadDisplayList);
		m_shaderFrontFinal.Unbind();

	}
	else
	{
		// DUAL DEPTH PEELING (not suited for transparent windows)

		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);

		// ---------------------------------------------------------------------
		// 1. Initialize Min-Max Depth Buffer
		// ---------------------------------------------------------------------
		
		glBindFramebuffer(GL_FRAMEBUFFER, m_dualPeelingSingleFboId);

		// Since we cannot blend the back colors in the geometry passes,
		// we use another render target to do the alpha blending
		glDrawBuffer(m_drawBuffers[6]);
		glClearColor(g_config.bgRedness, g_config.bgGreenness, g_config.bgBlueness, 0.5f);
		glClear(GL_COLOR_BUFFER_BIT);

		DrawBackground(&m_shaderSimple);

		StartProjection();

		// Render targets 1 and 2 store the front and back colors
		// Clear to 0.0 and use MAX blending to filter written color
		// At most one front color and one back color can be written every pass
		glDrawBuffers(2, &m_drawBuffers[1]);
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT);

		// Render target 0 stores (-minDepth, maxDepth, alphaMultiplier)
		glDrawBuffer(m_drawBuffers[0]);
		glClearColor(-MAX_DEPTH, -MAX_DEPTH, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT);
		glBlendEquation(GL_MAX);

		m_shaderDualInit.Bind();
		RecursiveDraw(g_config.resourceGroup, HXDRAW_DEPTH_INIT);
		m_shaderDualInit.Unbind();

		// ---------------------------------------------------------------------
		// 2. Dual Depth Peeling + Blending
		// ---------------------------------------------------------------------
		
		int currId = 0;

		bool useOQ = true;
		for (int pass = 1; useOQ && pass < g_config.maxDepthPasses; pass++) 
		{
			currId = pass % 2;
			int prevId = 1 - currId;
			int bufId = currId * 3;
		
			glDrawBuffers(2, &m_drawBuffers[bufId+1]);
			glClearColor(0, 0, 0, 0);
			glClear(GL_COLOR_BUFFER_BIT);

			glDrawBuffer(m_drawBuffers[bufId+0]);
			glClearColor(-MAX_DEPTH, -MAX_DEPTH, 0, 0);
			glClear(GL_COLOR_BUFFER_BIT);

			// Render target 0: RG32F MAX blending
			// Render target 1: RGBA MAX blending
			// Render target 2: RGBA MAX blending
			glDrawBuffers(3, &m_drawBuffers[bufId+0]);
			glBlendEquation(GL_MAX);

			m_shaderDualPeel.Bind();
			m_shaderDualPeel.BindTextureRECT("DepthBlenderTex", m_dualDepthTexId[prevId], 0);
			m_shaderDualPeel.BindTextureRECT("FrontBlenderTex", m_dualFrontBlenderTexId[prevId], 1);
			//m_shaderDualPeel.SetUniform("Alpha", (float*)&m_opacity, 1);
			RecursiveDraw(g_config.resourceGroup, HXDRAW_DEPTH_PEEL);
			m_shaderDualPeel.Unbind();

			// Full screen pass to alpha-blend the back color
			glDrawBuffer(m_drawBuffers[6]);

			glBlendEquation(GL_FUNC_ADD);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

			if (useOQ) glBeginQuery(GL_SAMPLES_PASSED_ARB, m_queryId);

			m_shaderDualBlend.Bind();
			m_shaderDualBlend.BindTextureRECT("TempTex", m_dualBackTempTexId[currId], 0);
			glCallList(m_quadDisplayList);
			m_shaderDualBlend.Unbind();

			if (useOQ)
			{
				glEndQuery(GL_SAMPLES_PASSED_ARB);
				GLuint sample_count;
				glGetQueryObjectuiv(m_queryId, GL_QUERY_RESULT_ARB, &sample_count);
				if (sample_count == 0) break;
			}
		}

		glDisable(GL_BLEND);

		// ---------------------------------------------------------------------
		// 3. Final Pass
		// ---------------------------------------------------------------------

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glDrawBuffer(GL_BACK);
		
		m_shaderDualFinal.Bind();
		m_shaderDualFinal.BindTextureRECT("DepthBlenderTex", m_dualDepthTexId[currId], 0);
		m_shaderDualFinal.BindTextureRECT("FrontBlenderTex", m_dualFrontBlenderTexId[currId], 1);
		m_shaderDualFinal.BindTextureRECT("BackBlenderTex", m_dualBackBlenderTexId, 2);
		glCallList(m_quadDisplayList);
		m_shaderDualFinal.Unbind();
	}

}

void HXDisplay::DrawBackground(HXShader* pShader)
{
	if (m_oglTexName // background
		&& !m_pImage) // not in transparent window mode
	{
		if (pShader) pShader->Bind();
		
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		//glEnable(GL_TEXTURE_2D);
		glLoadIdentity();
		glOrtho(0, 1, 0, 1, 0, 1);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		//glDisable(GL_LIGHTING);
		
		// No depth buffer writes for background
		//glDepthMask( false );
		if (pShader) 
		{
			pShader->BindTexture2D("SurfaceTex", m_oglTexName, 0);
		}
		else
		{
			glBindTexture( GL_TEXTURE_2D, m_oglTexName );
		}
		glBegin( GL_QUADS ); {
			
			//glNormal3f( 0.0f, 0.0f, 0.5f);	
			glTexCoord2f( 0.0f, 0.0f );
			glVertex2f(0,0);
			glTexCoord2f( 1.0f, 0.0f );
			glVertex2f(1.0,0);
			glTexCoord2f( 1.0f, 1.0f );
			glVertex2f(1.0, 1.0);
			glTexCoord2f( 0.0f, 1.0f );
			glVertex2f(0, 1.0);
		} glEnd();
		//glDepthMask( true );
		glPopMatrix();
		glPopMatrix();

		if (pShader) pShader->Unbind();
	}
}

void HXDisplay::StartProjection()
{
	// Redo the projection matrix (for non-background drawing)
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	GLdouble nearPlane = m_dimMax * m_nearZ - g_config.resourceGroup.transform.transZ;
	GLdouble farPlane = m_dimMax * m_farZ - g_config.resourceGroup.transform.transZ;
	gluPerspective(g_config.fov, static_cast<GLdouble>(m_width) / m_height, nearPlane, farPlane);
	glMatrixMode(GL_MODELVIEW);
}

void HXDisplay::RecursiveDraw(HXResourceGroup& rg, HXDrawMode drawMode)
{
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	if (!rg.transform.mvmIsCurrent && ((drawMode != HXDRAW_DEPTH_PEEL) && (drawMode != HXDRAW_FRONT_PEEL)))
	{
		HXMatrix3F rm1 = RotMatrixY(rg.transform.rotX);
		HXMatrix3F rm2 = RotMatrixX(rg.transform.rotY);
		HXMatrix3F rm3 = RotMatrixZ(rg.transform.rotZ);
		HXMatrix3F pose = rm1 * rm2 * rm3;
		rg.transform.mvm.SetIdentity();
		for (int i=0; i<3; i++)
		{
			for (int j=0; j<3; j++)
			{
				rg.transform.mvm[i][j] = pose[j][i];
			}
		}
		rg.transform.mvm[3][0] = rg.transform.transX;
		rg.transform.mvm[3][1] = rg.transform.transY;
		rg.transform.mvm[3][2] = rg.transform.transZ;
		HXMatrix4F scm;
		scm[0][0] = rg.transform.scaleX;
		scm[1][1] = rg.transform.scaleY;
		scm[2][2] = rg.transform.scaleZ;
		scm[3][3] = 1.0f;
		rg.transform.mvm = rg.transform.mvm * scm;
		rg.transform.mvmIsCurrent = true;
	}
	glMultMatrixf(&rg.transform.mvm[0][0]);

	/*glScalef(rg.transform.scaleX, rg.transform.scaleY, rg.transform.scaleZ);
	glTranslatef(rg.transform.transX, rg.transform.transY, rg.transform.transZ);
	glRotatef(rg.transform.rotX, 1, 0, 0);
	glRotatef(rg.transform.rotY, 0, 1, 0);
	glRotatef(rg.transform.rotZ, 0, 0, 1);
	HXMatrix4F mvm;
	mvm.SetIdentity();
	for (int i=0; i<3; i++)
	{
		for (int j=0; j<3; j++)
		{
			mvm[i][j] = m_pose[j][i];
		}
	}
	mvm[3][0] = g_config.resourceGroup.transform.transX;
	mvm[3][1] = g_config.resourceGroup.transform.transY;
	mvm[3][2] = g_config.resourceGroup.transform.transZ;
	glMultMatrixf(&mvm[0][0]);*/

	list<HXResource>::iterator resIt = rg.resources.begin();
	for (; resIt != rg.resources.end(); ++resIt)
	{
		if (resIt->pRender)
		{
			switch (drawMode)
			{
			case HXDRAW_LEGACY:
				resIt->pRender->DrawOpaque(NULL);
				resIt->pRender->DrawTransparent(NULL);
				break;
			case HXDRAW_SHADERS:
				resIt->pRender->DrawOpaque(&m_shaderPhong);
				resIt->pRender->DrawTransparent(&m_shaderPhong);
				break;
			case HXDRAW_DEPTH_INIT:
				resIt->pRender->Draw(&m_shaderDualInit);
				break;
			case HXDRAW_DEPTH_PEEL:
				resIt->pRender->Draw(&m_shaderDualPeel);
				break;
			case HXDRAW_FRONT_INIT:
				resIt->pRender->Draw(&m_shaderFrontInit);
				break;
			case HXDRAW_FRONT_PEEL:
				resIt->pRender->Draw(&m_shaderFrontPeel);
				break;
			}
		}
	}

	list<HXResourceGroup>::iterator chIt = rg.children.begin();
	for (; chIt != rg.children.end(); ++chIt)
	{
		RecursiveDraw(*chIt, drawMode);
	}

	glPopMatrix();
}


//! Kill 3D rendering display.
void HXDisplay::DestroyDisplay(bool bSubstantive)
{
	if (bSubstantive)
	{
		UnloadAllModels(g_config.resourceGroup);
		UnloadFont();
	}
	if (m_bDepthPeelingSupported)
	{
		DeleteDepthPeeling();
	}
	else
	if (m_bOpenGL2Supported)
	{
		DeleteOtherShaders();
	}
	DeleteMainFBO();

	list<HXRenderData>::iterator rdit = m_renderList.begin();
	for (; rdit != m_renderList.end(); ++rdit)
	{
	   rdit->ClearTextures();
	}
	if (m_oglTexName != 0)
	{
		glDeleteTextures(1, &m_oglTexName);
		m_oglTexName = 0;
	}
	//UnloadBackground();

	if (m_hglrc)
    {
        wglMakeCurrent(m_hdc, 0);
        wglDeleteContext(m_hglrc);
        m_hglrc = 0;
    }

    // disable OpenGL
	/*if (m_hdc)
    {
        if (m_hglrc)
        {
            wglMakeCurrent(m_hdc, 0);
            wglDeleteContext(m_hglrc);
            m_hglrc = 0;
        }

        ReleaseDC(m_hwnd, m_hdc);
        m_hdc = 0;
		m_hwnd = 0;
    } */

	g_window.ShowStatus(L"OpenGL display disabled");
}